feat(hooks): async useViewModelInstanceAsync (deprecate sync hook)#305
feat(hooks): async useViewModelInstanceAsync (deprecate sync hook)#305mfazekas wants to merge 3 commits into
Conversation
The sync useViewModelInstance creates instances via deprecated runtime
APIs that touch the Rive runtime on the JS thread. Add
useViewModelInstanceAsync built on the non-deprecated `*Async` APIs,
returning a { instance, isLoading, error } discriminated union (mirroring
useRiveFile), with cancellation/disposal on deps change + unmount and
onInit applied before the instance is exposed.
Keeps `required` parity (throws once resolved to null; undefined while
loading). The sync hook is marked @deprecated but left working for
backward compatibility. Migrates the example screens to the async hook.
|
Reviewed with a focus on correctness and on whether a backward-compatible path (evolving Bugs / behavior
Structure
On a backward-compatible approachI checked whether One naming consideration: once the sync hook is removed, the permanent API carries an Checked and fineThe one-commit window where a just-swapped source still exposes the previous instance is exactly the scenario |
Address review findings on #305: - Map a rejected artboard lookup to the friendly "Artboard 'X' not found" error (the new native backend rejects on a missing artboard instead of resolving undefined; includes the native message). - Propagate createInstanceByNameAsync rejections into the error result instead of console.warn + a misleading "instance not found" label. - QuickStart: render useRiveFile/useViewModelInstanceAsync errors so a failing onInit no longer yields a silent blank screen. - Document that a null source stays isLoading forever and the upstream file error must be checked (with an updated JSDoc example). Adds 3 tests that reproduced the rejection-path bugs before the fix.
Address the review findings on the async hook (same issues as #305, with the design refined for this branch's native backends): - Map a rejected artboard lookup to the friendly "Artboard 'X' not found or has no ViewModel" error — both platforms throw on an unknown artboard name rather than resolving undefined. A rejection with no artboardName still propagates unchanged. - Keep the stable "ViewModel instance 'X' not found" message when createInstanceByNameAsync rejects, but attach the native error as cause instead of console.warn-ing it away. - Settle to a terminal { instance: null, isLoading: false } when the source is null (useRiveFile's error state) instead of loading forever; undefined still means loading, mirroring useRiveFile's convention. - QuickStart: surface useRiveFile/useViewModelInstanceAsync errors so a failing onInit no longer yields a silent blank screen. - Add an on-device e2e harness covering the real native rejection paths the Jest mocks can't.
Address the review findings on the async hook (same issues as #305, with the design refined for this branch's native backends): - Map a rejected artboard lookup to the friendly "Artboard 'X' not found or has no ViewModel" error — both platforms throw on an unknown artboard name rather than resolving undefined. A rejection with no artboardName still propagates unchanged. - Keep the stable "ViewModel instance 'X' not found" message when createInstanceByNameAsync rejects, but attach the native error as cause instead of console.warn-ing it away. - Settle to a terminal { instance: null, isLoading: false } when the source is null (useRiveFile's error state) instead of loading forever; undefined still means loading, mirroring useRiveFile's convention. - QuickStart: surface useRiveFile/useViewModelInstanceAsync errors so a failing onInit no longer yields a silent blank screen. - Add an on-device e2e harness covering the real native rejection paths the Jest mocks can't.
Address the review findings on the async hook (same issues as #305, with the design refined for this branch's native backends): - Map a rejected artboard lookup to the friendly "Artboard 'X' not found or has no ViewModel" error — both platforms throw on an unknown artboard name rather than resolving undefined. A rejection with no artboardName still propagates unchanged. - Keep the stable "ViewModel instance 'X' not found" message when createInstanceByNameAsync rejects, but attach the native error as cause instead of console.warn-ing it away. - Settle to a terminal { instance: null, isLoading: false } when the source is null (useRiveFile's error state) instead of loading forever; undefined still means loading, mirroring useRiveFile's convention. - QuickStart: surface useRiveFile/useViewModelInstanceAsync errors so a failing onInit no longer yields a silent blank screen. - Add an on-device e2e harness covering the real native rejection paths the Jest mocks can't.
Address the review findings on the async hook (same issues as #305, with the design refined for this branch's native backends): - Map a rejected artboard lookup to the friendly "Artboard 'X' not found or has no ViewModel" error — both platforms throw on an unknown artboard name rather than resolving undefined. A rejection with no artboardName still propagates unchanged. - Keep the stable "ViewModel instance 'X' not found" message when createInstanceByNameAsync rejects, but attach the native error as cause instead of console.warn-ing it away. - Settle to a terminal { instance: null, isLoading: false } when the source is null (useRiveFile's error state) instead of loading forever; undefined still means loading, mirroring useRiveFile's convention. - QuickStart: surface useRiveFile/useViewModelInstanceAsync errors so a failing onInit no longer yields a silent blank screen. - Add an on-device e2e harness covering the real native rejection paths the Jest mocks can't.
Address the review findings on the async hook (same issues as #305, with the design refined for this branch's native backends): - Map a rejected artboard lookup to the friendly "Artboard 'X' not found or has no ViewModel" error — both platforms throw on an unknown artboard name rather than resolving undefined. A rejection with no artboardName still propagates unchanged. - Keep the stable "ViewModel instance 'X' not found" message when createInstanceByNameAsync rejects, but attach the native error as cause instead of console.warn-ing it away. - Settle to a terminal { instance: null, isLoading: false } when the source is null (useRiveFile's error state) instead of loading forever; undefined still means loading, mirroring useRiveFile's convention. - QuickStart: surface useRiveFile/useViewModelInstanceAsync errors so a failing onInit no longer yields a silent blank screen. - Add an on-device e2e harness covering the real native rejection paths the Jest mocks can't.
Address the review findings on the async hook (same issues as #305, with the design refined for this branch's native backends): - Map a rejected artboard lookup to the friendly "Artboard 'X' not found or has no ViewModel" error — both platforms throw on an unknown artboard name rather than resolving undefined. A rejection with no artboardName still propagates unchanged. - Keep the stable "ViewModel instance 'X' not found" message when createInstanceByNameAsync rejects, but attach the native error as cause instead of console.warn-ing it away. - Settle to a terminal { instance: null, isLoading: false } when the source is null (useRiveFile's error state) instead of loading forever; undefined still means loading, mirroring useRiveFile's convention. - QuickStart: surface useRiveFile/useViewModelInstanceAsync errors so a failing onInit no longer yields a silent blank screen. - Add an on-device e2e harness covering the real native rejection paths the Jest mocks can't.
Address the review findings on the async hook (same issues as #305, with the design refined for this branch's native backends): - Map a rejected artboard lookup to the friendly "Artboard 'X' not found or has no ViewModel" error — both platforms throw on an unknown artboard name rather than resolving undefined. A rejection with no artboardName still propagates unchanged. - Keep the stable "ViewModel instance 'X' not found" message when createInstanceByNameAsync rejects, but attach the native error as cause instead of console.warn-ing it away. - Settle to a terminal { instance: null, isLoading: false } when the source is null (useRiveFile's error state) instead of loading forever; undefined still means loading, mirroring useRiveFile's convention. - QuickStart: surface useRiveFile/useViewModelInstanceAsync errors so a failing onInit no longer yields a silent blank screen. - Add an on-device e2e harness covering the real native rejection paths the Jest mocks can't.
What
Adds
useViewModelInstanceAsync, an async replacement foruseViewModelInstancebuilt on the non-deprecated*Asyncruntime APIs (viewModelByNameAsync,defaultArtboardViewModelAsync,createInstanceByNameAsync,createDefaultInstanceAsync,createBlankInstanceAsync).Why
The sync hook creates instances via
@deprecatedAPIs that access the Rive runtime on the JS thread (it ships with aneslint-disable @typescript-eslint/no-deprecatedand a migration TODO). The async variant resolves off-thread and drops the deprecated path.API
Returns a
{ instance, isLoading, error }discriminated union mirroringuseRiveFile. Same overloads/params as the sync hook includingrequired(throws once resolved tonull;undefinedwhile loading). Lifecycle usesuseState/useEffectwith a cancellation flag andcallDisposeon deps-change/unmount;onInitruns before the instance is exposed.Backward compatible: the sync
useViewModelInstanceis marked@deprecatedbut kept working. Example screens migrated to the async hook (dataBind={instance}unchanged).Verify
yarn tsc,yarn lint(0 errors),yarn jest(65 passed incl. 16 new async tests).Note: same change is also present in #304 (which targets the experimental branch); this PR lands it on main independently.